useEffect Notes
useEffect is called on initial load and every update after unless an empty dependency array is entered.. then it is only performed on first load:
|
1 2 3 4 5 6 7 8 9 |
useEffect(()=>{ //this will only be performed on first load },[]); useEffect(()=>{ //this will only be performed on first load and everytime // someVariableThatHasChanged has been updated },[someVariableThatHasChanged]); |
For ajax calls on initial loads or in useEffect in general..async/await is only allowed if done in this way:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//below is allowed useEffect(()=>{ async function getData(){ //get ajax data } let returnedData=await getData(); }); // Having async in the way below is not allowed. useEffect(async ()=>{ let returnedData=await getData(); }); |
useEffect & setState
useEffect generally would get the “stale” state value however we can pass in a callback to the setState function in order to recieve/use the previous states value
|
1 2 3 4 5 6 7 8 9 10 11 |
useEffect(()=>{ //normally using setState as below wouldn't work //because the actual value in someState is "stale" setSomeState(someState+1); // will always return 1 because the stale someState would be it's initial value of 0 //below would work because the callback provides the actual previous state value setSomeState(someState =>someState+1); },[someState]); |
The remaining problem with this is that you can only access the previous state value if you are setting (setSomeState) it. One work around that is provided is to use Refs
Using useRefs for current state value in useEffect
Refs exist outside of the re-render cycle and there for their actual current value isn’t effected by re-renders and so the value at ref.current will stay “current”. So the ref.current value will be what it was set last and not being reset or altered by a re-render. However, refs aren’t reactive.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function Timer() { const [count, setCount] = React.useState(0) const countRef = React.useRef(0) React.useEffect(() => { const intervalId = setInterval(() => { countRef.current = countRef.current + 1 setCount(countRef.current) // we don't need the call back to reference // the previous state in order to update it properlly because we use ref.current which // retains it's value and isn't affect by the re-renders. }, 1000) return () => clearInterval(intervalId) }, []) return ( <div>The count is: {count}</div> ) } |
The problem with this approach is that refs aren’t reactive and so shouldn’t be used like how <div>The count is: {count}</div> is displayed because the actual value that is displayed (the counting would still happen behind the scene tho ) won’t update until the component is re-rendered.
React Router
Import react-router-dom to use for web. Wrap <BrowserRouter></BroswerRouter> around <App/> in the index.js
To create routes you would do the following:
|
1 2 3 4 5 6 7 8 9 10 |
<Route exact path="/" component={TestComponent} /> <Route exact path="/test2" component={TestComponent2} /> <Route exact path="/test3" component={TestComponent3} /> <Route exact path="/test4" component={TestComponent4} /> // this is inclusive so any and all patterns match will be included if "exact" // keyword isn't used. // Can also wrap all of the routes in a <Switch></Switch> component // and that will render only the FIRST to match the pattern exclusively |
Link vs NavLink
To link to a route you can use Link or NavLink. The difference mostly is that Link will just create a <a> element pointing to the route ( which doesn’t actually issue a GET request like a link normally would ). Navlink will do the same thing but it is to be used in menus because you can set a prop in Navlink to indicate the “active” route so it can be styled properly.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<Link to="/">Test1</Link> // creates simple <a> to route <NavLink to="/" activeClassName="active-route">Test1</NavLink> // above will put a class of active-route on the link it produces // so it can be styled different using CSS and classes <NavLink to="/" activeStyle={{ color:'red' }}>Test1</NavLink> // activeStyle is a inline prop where you can specify what // css should be applied if it is the active route. |
Passing a prop to a route ( render vs component props )
|
1 2 3 4 5 6 7 8 9 10 11 |
// below will pass a prop to the route but it is the wrong // way of doing so because the component will be recreated // every time going through the whole life cycle <Route exact path="/" component={()=><TestComponent name="jason"/>} /> // we can prevent the needless re-render by using render instead: <Route exact path="/" render={()=><TestComponent name="jason"/>} /> // this will make it so the component isn't needlessly re-rendering. |
URL Parameters with Router
When a route is declared and component/render/children is used, 3 things are passed to component/render/children under one component.. — history, location , match
So we can do the following to extract the parameter in a given route
|
1 2 3 4 5 6 7 8 9 |
<Route exact path="/:name" render={(routeProps)=> <TestComponent name={routeProps.match.params.name/>} /> // in path, :name is the parameter for the route // when using render/children/component a route prop is // supplied (in our case it's called routeProps) with // the parameters available under // routeProps.match.params.name |
Redirects
Can use Redirect component from react-router-dom .
|
1 2 3 |
// Simply doing below will bring the user to the route for '/' <Redirect to='/' /> |
Redirect programmatically
This is done by pushing on the browser history stack. In order to be able to do that, the routeProps has to be passed down when the <Route/> is created. This is done automatically if using componenet={SomeComponent} but needs to be passed manually if done via render instead. i.e. You would only need to use render if you want to pass other props to the Route’s component.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
props.history.push('/') // will bring to base url // this will only work if you pass in route props.. //i.e. when defining the route <Route path="/" render={(routeProps)=>{<SomeComponent {...routeProps}/>}} // now you can push to the routeProps history // since SomeComponent has routeProps passed to it. // and it will bring you to '/' // NOTE - if you use component instead of render then //i.e. <Route path="/" component={SomeComponent}/> // routeProps is already passed in and can be used // to push on history |
Some differences of history.push vs <Redirect>
<Redirect /> won’t change the back button so if you redirect to a url that goes to a 404 then hitting the back button won’t bring them back to the url that will re-redirect to the 404 page. history.push will make the back button to the route that was pushed – so, if history.push to a route that goes to a 404 then hitting the back button will bring it back to the route that took it to the 404 and will be a endless loop.
Redirecting via history.push when the component has no route its self and so no routeProps – use withRouter
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from 'react'; import {withRouter} from 'react-router-dom'; const RandomPropThatHasNoRoute=(props)=>{ return (<button onClick={e=>{props.history.push('/newPage');}} > Click to redirect </button> } export default withRouter(RandomPropThatHasNoRoute); // the history push will only work if withRouter is set as above // because otherwise this componenet that has no <Route/> will not // be given the routeprops(history). |
props.history.goBack() and props.history.goFoward()
These methods are available if needed to create functionality in the app similiar to the browsers forward and back button.
React Context API
Components can easier share values/functions by being wrapped in a Context.Provider component. Any component that uses that particular Context will be re-rendered when that Context’s value changes. This may cause un-wanted re-renders.
An example of using the Context Hook:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// in a seperate file (context.js) we initialize the context import React from 'react'; export const todoContext=React.createContext(); // then in container components or App.js/index.js we can // wrap other components with the created context provider and pass // along objects to any children components so that the // values are available // App.js import React from 'react'; import ReactDom from 'react-dom'; import {todoContext} from './contexts.js' const App=()=>{ return ( <> <h1>Main App</> <todoContext.Provider value={{count:100}}> //now count is available in // child component(s) <ChildComponent1 /> <ChildComponent2 /> </todoContext> </> ) } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement); // then in the child component you can consume the value(count) via: // Childcomponent1.js import React from 'react'; import todoContext from './contexts.js'; const ChildComponent1=()=>{ // we retrieve and deconstruct the value object to extract count const {count}=React.useContext(todoContext); return (<p> Showing the count retrieved from Context {count} </p>) } export default ChildComponent1; |
One issue with above is the todoContext using an object for the values ( values={{count:100}} ) , this will cause needless re-renders because the object will be rebuilt every time.
Setting Disabled attribute based on a function (computed property) that returns a boolean
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// validateNames is the function we want to run to check if 2 state vars // are set and return true or false based on that. const validateNames = () => { if (pluginMeta.className != "" && pluginMeta.nonceName != "") { return false; } else { return true; } }; // we use validateNames in a ternary to create an object // containing disabled if validatesNames returns true // it will return an empty object ( not disabling) if false let disabled = validateNames() ? { disabled: "disabled" } : {}; // finally, we spread the disabled variable as below which // will either add the disabled attribute or nothing ( empty object ) <Button onClick={handleCloseModal} {...disabled}>Finished</Button> |
Conditionally show a component based on a state array not being empty.
Casting the input.Values.length as a Boolean enables this to be possible. Without casting , it would return a 0 to the screen which is not what is desired.
|
1 2 3 4 5 |
{inputValues && Boolean(inputValues.length) && ( <> <p>This will only show if the array exists and isn't empty.</p> </> )} |