React, how to make state work between childs? - reactjs

I am trying to figure out how to make a searchFilter work. Here is my situation:
In App.js I hold a state for my items that i get from an api.
I also hold a state for the SearchFilter.
The items arrive and I can render them just fine.
Further, in App.js, I render the items and also a search component. So my code looks something like this:
const App = () => {
const [items, setItems] = useState([])
const [searchFilter, setSearchFilter] = useState("")
useEffect(() => {
const fetchItems = async () => {
// FETCHING ITEMS AND SETTING VIA setItems...
// This part works as expected
}
fetchItems()
},[])
return (
<>
<SearchBar setSearchFilter={setSearchFilter} />
<RenderItems items={items} searchFilter={searchFilter} />
</>
)
}
The problem I face is, that the searchFilter remains undefined in the RenderItems component. Why?
It gets updated correctly in App.js, but somehow doesn't make it's way to RenderItems
Inside component SearchBar:
const SearchBar = ({setSearchFilter}) => {
return (
<>
<input type="text" placeholder="Search" onChange={(e) => setSearchFilter(e.target.value) }/ >
</>
)
{
Any clues?

Thank you all for the replies #Mandeep Kaur and #KcH
I found the problem was in the data that came from the api when trying this scenario out in a codesandbox.
I keep the link here for future reference: https://codesandbox.io/s/nostalgic-booth-p1tqsv?file=/src/App.js
Closed from my side.

I think this happens because RenderItems component is not re-render after updating the state in SearchBar component.
You can try with adding one useEffect that makes it re-render and it gives the latest data to RenderItems
useEffect(() => {
},[searchFilter])
I am assuming that the updated value you getting in the App.js file.

Related

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

Passing Array of Items - React

I'm attempting to get data from an array from ComponentOne to ComponentTwo. The state lives in App.js. This is sort of working as I can see the data when console.logging from ComponentTwo. However the data does not show on screen with the <p> {props.PhaseThreeItems[0] } </p> code.
Two questions:
#1: Am I going about this the correct way?
#2: Why is the data not showing on screen?
// App.js:
const [phaseThreeArray, setPhaseThreeArray] = useState([])
<ComponentOne PhaseThreeArrayProp={phaseThreeArray}/>
<ComponentTwo PhaseThreeItems={phaseThreeArray}/>
...
// ComponentOne
const checkInput = () => {
props.PhaseThreeArrayProp.push("my data");
}
...
// ComponentTwo
const showData = () => {
console.log(props.PhaseThreeItems[0])
}
<p> {props.PhaseThreeItems[0] } </p>
React ... reacts... to state changes. This means that you have to tell react "this has updated", via your setPhaseThreeArray property. What you have done is manually call push, rather than setting the state. React isn't aware of any data changes, and as such, doesnt update.
To fix this, you need to set the state using the function. The quickest way of doing it would be:
// App.js:
const [phaseThreeArray, setPhaseThreeArray] = useState([])
<ComponentOne setPhaseThreeArray={setPhaseThreeArray} PhaseThreeArrayProp={phaseThreeArray}/>
<ComponentTwo PhaseThreeItems={phaseThreeArray}/>
// ComponentOne
const checkInput = () => {
props.setPhaseThreeArray([...props.PhaseThreeArrayProp, "my data"])
}
This will set the state with the new array.

How to rerender with setState using the same value in MaterialUi Snackbar

I have an Input and when the user submits a value which does not exist a not found message is shown for example setError("Not found") in the Toast (Snackbar) which closes after timeout. I am using Material UI and i am using it as its shown in this example Consecutive Snackbars in the docs. Only difference i made is i put this in a seperate component and added useEffect like this
const Toast = ({message}) => {
React.useEffect(() => {
if(message) {
setSnackPack((prev) => [...prev, { message, key: new Date().getTime() }]);
}
}, [message])
... // Rest of the code from the docs
}
And this works when the error is set but if the same errors occurs (same value) it wont show the value (Snackbar)like in the example as it will not rerender because it has the same value.
My question is how would i cause the rerender so that the same string appears again, or is there a different way I could do something like this as I feel this is an anti-pattern?
I'm not 100% sure I understood your problem, but I suspect you might not be managing the Snackbar's open state properly. I suggest making sure that it's always set (or inherited from the parent) correctly (e.g. not just set once upon state initialization).
The code below allows displaying a Snackbar with identical parameters multiple times. See this codesandbox for a fully working example.
import { useState } from "react";
import Snackbar from "#material-ui/core/Snackbar";
export default function App() {
const [text, setText] = useState();
const [show, setShow] = useState(false);
return (
<>
<input onChange={(e) => setText(e.target.value)} />
<button onClick={() => setShow(true)}>Show it</button>
<Toast show={show} hide={() => setShow(false)} message={text} />
</>
);
}
const Toast = ({ message, show, hide }) => {
return (
<Snackbar
open={show}
autoHideDuration={1000}
onClose={hide}
message={message}
/>
);
};
You could change message to an object
let message = {
messageText: "A bad thing happened...",
timestamp: 1626698144204
}
Then in your handler, update the timestamp. Then the useEffect should fire when the timestamp gets updated.

Avoid stale data prop in React child component

From my parent component, I am passing down the state variable data as a prop to the child component. The data is fetched via fetch/UseEffect. The URL params can be modified by clicking a button, and depending on the param, a completely different object is fetched, which needs to be handled differently in the child component.
export default function ParentComponent() {
const [data, setData] = useState({})
const [param, setParam] = useState('')
const [isLoading, setLoading] = useState(true)
useEffect(() => {
const fetchData = async () => {
const data= await (await fetch(`https://myapi.com/?param=${param}`)).json()
setData(data)
setLoading(false)
}
fetchData()
}, [param])
return (<div>
{ isLoading ?
(<span>Loading...</span>)
:
(<div>
<button onClick={() => setParam('something')}>Click me</button>
<ChildComponent data={ data } />
</div>
</div>)
}
}
My problem is that whenever I click the button (set the state) in the parent, which triggers a rerender, the components rerender twice (causing below console.log to print twice), which I'd dare say is expected behavior, once because of setParam, once when fetchData and setData is completed).
export default function ChildComponent(props) {
const { data } = props
return (
<div>
// Prints twice, first stale data, then when fetchData has completed.
{ console.log(data )}
</div>)
}
My question to you guys, as I have been struggling with this for a couple of hours now, having read ChrisW's post here (Avoid old data when using useEffect to fetch data) and the React.js Hooks FAQ (https://reactjs.org/docs/hooks-faq.html), is how on God's green earth (sorry any atheist!) I only ever get to access the newly fetched, non-stale data prop in the child component, and ignore the stale one? Is it through refs? UseEffect? (I know I can make a UseEffect in the child component with data as a dependency, however, what should I do with it, I am after all trying to control what is being returned?)
Thanks in advance for any answers!
I would suggest to consider useMemo API to solve this issue:
const MemoizedChildComponent = useMemo(({ data }) => <ChildComponent data={data} />, [data]);

React Hooks Wrapper not getting updated after state change in useEffect

Current behaviour
I'm using a functional component with a setState hook in useEffect. The state variable that is set inside useEffect is wrapped over the return statement to render the JSX for the component.
When I debug into it, the component renders with the correct state variable but my wrapper in my test does Not show the correct information.
wrapper.update() isn't fixing this issue.
Below is a snippet of what I am trying to achieve:
const DummyComponent= ({}) => {
const [selected, setSelected] = React.useState(false);
useEffect(() => {
setSelected(true)
}, [someDependency])
return (
{
selected && (
<div id= 'container'>
{childComponents}
</div>)
}
);
})
it('test', () => {
const wrapper= mount( <DummyComponent /> );
wrapper = wrapper.update(); // this doesn't fix my problem
wrapper.find('#container')first().props().onClick();
expect(wrapper.toMatchSnapshot());
});
I am getting the below error:
Method “props” is meant to be run on 1 node. 0 found instead.
Expected Behaviour
After state update in useEffect re-render should be triggered in test case and element with id="container" should be found.
Note: This is not same as https://github.com/enzymejs/enzyme/issues/2305
 
It seems to me there's some other problem with your real code (maybe some promise-based code invoked in the effect?). Here's a working example based on your snippet:
const DummyComponent = ({}) => {
const [selected, setSelected] = React.useState(false);
const [result, setResult] = React.useState("");
React.useEffect(() => {
setSelected(true);
}, []);
return selected && <div id='container' onClick={() => setResult("test")}>
<label>{result}</label>
</div>;
};
it('test', () => {
const wrapper = mount(<DummyComponent/>);
act(() => {
wrapper.find('#container').first().props().onClick();
});
expect(wrapper.find("label").text()).toEqual("test");
});
The act is actually needed only for interaction with the component itself, not for after-render effect.
The problem is that when you first mount the component, it does not render anything, because selected is false. So, when you search for '#container', you don't get anything.
If the update is enough, then it should probably be executed before the wrapper.find(), so that the component is rendered with selected true. But React is asynchronous and I suspect that this will not be enough…
I fixed my problem, actually I need to assign my component to a different wrapper and then update the wrapper and then check for updates on the wrapper instead of the component. Below is the snippet:
it('test', () => {
const component= mount( <DummyComponent /> );
const wrapper = component.update();
wrapper.find('#container')first().props().onClick();
expect(wrapper.toMatchSnapshot());
});
This will have the updated component

Resources