I'm trying to use the function below (renderMatchedLogs) to render values from the object it receives, and I'm able to console.log the values but nothing displays on the screen.
I thought JSX can be rendered on the screen from another function? But I'm not sure if this something I misinterpreted or if my logic is off.
Further details of the code:
In the render() {} portion of the code:
<button onClick={this.findMatches}>Find Matches</button>
Which triggers this function to find matches:
findMatches = () => {
const foodLog = this.state.foodLog;
const foodFilter = this.state.foodFilter;
console.log("food filter", foodFilter);
Object.keys(foodLog).map((key, index) => {
if (foodLog[key].foodSelectedKey.some((r) => foodFilter.includes(r))) {
const matchedLog = foodLog[key];
this.renderMatchedLogs(matchedLog);
} else {
// do nothing
}
});
};
And then this is the function to render the values:
renderMatchedLogs = (matchedLog) => {
return (
<div>
{matchedLog.dateKey}
<br />
{matchedLog.mealKey}
<br />
{matchedLog.foodSelectedKey}
<br />
{matchedLog.reactionKey}
<br />
</div>
);
};
You’re rendering it, but not telling the application where to put it. I’d recommend putting the matchedLogs items in state somewhere that you update when you call findMatches, and then within your actual component have a something that looks like this
<div>
{matchedLogs && (renderMatchedLogs())}
<div>
Which can be the same as you have, apart from it’ll read the actual data from the state and render it rather than doing all of that itself (as I’m seeing from this context you want that to be user triggered).
May be what you're looking for is something like this?
state = {
logs: null
}
renderMatchedLogs = (matchedLog) => {
return (
<div>
{matchedLog.dateKey}
<br />
{matchedLog.mealKey}
<br />
{matchedLog.foodSelectedKey}
<br />
{matchedLog.reactionKey}
<br />
</div>
);
};
findMatches = () => {
const foodLog = this.state.foodLog;
const foodFilter = this.state.foodFilter;
console.log("food filter", foodFilter);
const matchedLogs = [];
//use forEach instead to push to matchedLogs variable
Object.keys(foodLog).forEach((key, index) => {
if (foodLog[key].foodSelectedKey.some((r) => foodFilter.includes(r))) {
const matchedLog = foodLog[key];
// this will return the div element from renderMatchedLogs
matchedLogs.push(this.renderMatchedLogs(matchedLog));
}
});
const logs = (<>
{matchedLogs.map(div => div)}
</>);
this.setState({
logs
})
};
render(){
return (
<>
{this.state.logs}
<button onClick={this.findMatches}>Find Matches</button>
</>
)
}
Related
Got a question - the App function below has a 'filteredUsers' method that gets executed every time a pass is made - so when a search state is set it actually runs the method and its result is passed as a prop to the 'List' functional component and all is well. How do I change this into an older style React.Component so this still works? (as per my attempt below)
const App = () => {
const [text, setText] = React.useState('');
const [search, setSearch] = React.useState('');
const handleText = (event) => {
setText(event.target.value);
};
const handleSearch = () => {
setSearch(text);
};
console.log('*** App ***'); // each time as I type this is shown
const filteredUsers = users.filter((user) => {
console.log('Filter function is running ...'); // each time this is shown
return user.name.toLowerCase().includes(search.toLowerCase());
});
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleSearch}>
Search
</button>
<List list={filteredUsers} />
</div>
);
};
const List = ({ list }) => {
return (
<ul>
{list.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
};
const ListItem = ({ item }) => {
return <li>{item.name}</li>;
};
Then in this React.Component equivalent (App3) I am trying this:
Now how do I get a filtered list passed to the List component when I hit the search button?
class App3 extends React.Component {
state = {
text: '',
search: '',
}
handleText(event) {
this.setState({ text: event.target.value });
}
handleSearch() {
this.setState({ search: this.state.text });
}
filteredUsers = users.filter((user) => {
console.log('Filter function is running ...');
return user.name.toLowerCase().includes(this.state.search.toLowerCase());
});
render() {
return (
<div>
<input type="text" value={this.state.text} onChange={this.handleText.bind(this)} />
<button type="button" onClick={this.handleSearch.bind(this)}>
Search
</button>
<List list={this.filteredUsers} />
</div>
)
}
}
In your first version, since your component renders, the filteredUsers variable gets updated in every render, so you get the filtered data. You can use useMemo there also to make it slightly better.
In your second (class component) version, this variable is not getting updated. So, you can make it a function and invoke it to pass the list prop:
filteredUsers = () => // make function
users.filter((user) => {
console.log("Filter function is running ...");
return user.name.toLowerCase().includes(this.state.search.toLowerCase());
});
render() {
return (
<div>
<input
type="text"
value={this.state.text}
onChange={this.handleText.bind(this)}
/>
<button type="button" onClick={this.handleSearch.bind(this)}>
Search
</button>
<List list={this.filteredUsers()} /> // invoke
</div>
);
}
or you can move it into the render method and assign to a variable:
render() {
const filteredUsers = users.filter((user) => {
console.log("Filter function is running ...");
return user.name.toLowerCase().includes(this.state.search.toLowerCase());
});
return (
<div>
<input
type="text"
value={this.state.text}
onChange={this.handleText.bind(this)}
/>
<button type="button" onClick={this.handleSearch.bind(this)}>
Search
</button>
<List list={filteredUsers} />
</div>
);
}
Though, if it is not mandatory, you should go with the first version since class components are not the way to go for a new component anymore.
Here is the version with useMemo:
const filteredUsers = React.useMemo(
() =>
users.filter((user) => {
console.log("Filter function is running ..."); // each time this is shown
return user.name.toLowerCase().includes(search.toLowerCase());
}),
[search]
);
In this case, this variable is only evaluated when search changes instead of every state change.
I am trying to get the same number (input and div) in Function Component when clicking on a button, is it possible?
function App() {
let [someState, setSomeState] = useState(5)
let [otherSomeState, setOtherSomeState] = useState(7)
let stateChanger = () => {
setSomeState(otherSomeState + someState)
setOtherSomeState(someState)
}
return (
<div className="App">
<div>{someState}</div>
<input value={otherSomeState} onChange={ ()=>false } />
<button onClick={ ()=>stateChanger() }>Add</button>
</div>
);
}
As I understand it, I need to call the component's render again, but I don't understand how to do it.
I have a functional component with the following hook:-
const [imagePreview, setImagePreview] = useState('');
Later on, the change event, I have this:-
if (imagePreviewUrl) {
setImagePreview( (<div>
<br/> <img src={imagePreviewUrl} alt="icon" width="200" />
</div>));
}
Looks like it doesn't set anything. how do I set it? I could do it as:-
let imagePreview = null;
if (imagePreviewUrl) {
imagePreview = (<div>
<br/> <img src={imagePreviewUrl} alt="icon" width="200" />
</div>);
}
But it is bit ugly. I think useState is the recommended way to do it.
Update with more code:-
const fileChangedHandler = event => {
setSelectedFile(event.target.files[0]);
let reader = new FileReader();
reader.onloadend = () => {
setImagePreviewUrl(reader.result);
}
reader.readAsDataURL(event.target.files[0])
if (imagePreviewUrl) {
// can't break into multiline, sytax error
setImagePreview('(<div><br/> <img src={imagePreviewUrl} alt="icon" width="200" /> </div>)');
}
return (
<div>
<label>Profile picture: </label>
<input className="Column" type="file" accept="image/*"
onChange={(e) => {fileChangedHandler(e)}} />
// I want to show the selected image here as preview before upload
{ imagePreview }
</div>
)
Hopefully, I will give a clear understanding. What is my intention?
The reason you're having issues is because you're trying to access the state of imagePreviewUrl right after you update it with setImagePreviewUrl - this will not work.
const fileChangeHandler = (event) => {
// ...
reader.onloadend = () => {
// this is asynchronous
// so this definitely wouldn't get
// executed before you get to the `if` statement below
// also, `setImagePreviewUrl` is asynchronous as well
setImagePreviewUrl(reader.result)
}
// ...
if (imagePreviewUrl) {
// the old value of imagePreviewUrl is being used
// here, and since the initial value evaluates to
// `false`, the `setImagePreview` call below
// never gets executed
}
}
Your new value for imagePreviewUrl won't be available immediately because it's asynchronous, so you should probably create a separate hook that listens for changes to imagePreviewUrl and updates imagePreview.
function App() {
const [imagePreviewUrl, setImagePreviewUrl] = React.useState("");
const [imagePreview, setImagePreview] = React.useState(null);
const fileChangedHandler = () => {
setImagePreviewUrl(
"https://interactive-examples.mdn.mozilla.net/media/examples/grapefruit-slice-332-332.jpg"
);
};
React.useEffect(() => {
if (imagePreviewUrl !== "") {
const image = (
<div>
<img alt="fruit" src={imagePreviewUrl} />
</div>
);
setImagePreview(image);
}
}, [imagePreviewUrl]);
return (
<div className="App">
<button type="button" onClick={fileChangedHandler}>
Trigger file change handler.
</button>
{imagePreview}
</div>
);
}
Here's a working example:
CodeSandbox
This is not a recommended approach, what you want is a conditional rendering:
const [imagePreview, setImagePreview] = useState(false);
if (imagePreviewUrl) {
setImagePreview(true);
}
return (
imagePreview && (
<div>
<br /> <img src={imagePreviewUrl} alt="icon" width="200" />
</div>
)
);
I wonder why my component SearchResults is rendered twice.
In MainPage component I want to pass offers to child component SearchResults:
const mainPage = () => {
const [offers, setOffers] = useState(null);
useEffect(() => {
onInitOffers();
}, [])
const onInitOffers = () => {
axios.get('/offers')
.then(response => {
setOffers(response.data);
})
.catch(error => {
console.log(error);
})
}
const searchResults = (
<SearchResults
searchedOffers={offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{searchResults}
</div>
</div>
</Aux>
)
}
export default mainPage;
Why the component SearchResults is rendered twice? How to correctly pass offers to child component using hooks?
In my child component SearchResults I have to add if condition to avoid error map is not a function:
const searchResults = props => {
useEffect(() => {
console.log("RENDER");
console.log(props.searchedOffers) --> null for the first time
}, [props.searchedOffers]);
let offers = null;
if (props.searchedOffers !== null) { --> props.searchedOffers is not null after the second render
offers = props.searchedOffers.map(offer => {
return (
<Grid key={offer.id}>
<SearchResult key={offer.id} offer={offer}/>
</Grid>
)
});
}
It's rendered twice because, when the element mounts, you set offers to null. If you want to make sure you only render the SearchResults component when offers isn't null, you can do something like:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers && <SearchResult searchedOffers={offers} />}
</div>
</div>
</Aux>
)
If you want to be super sure offers is an array, you can do something like {Array.isArray(offers) && <SearchResult searchedOffers={offers} />}.
Often when doing something async like this, you might elect to actually use a ternary operator to show a loading indicator while the fetch is happening:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers ? <SearchResult searchedOffers={offers} /> : "Loading..."}
</div>
</div>
</Aux>
)
I want to make a little react application that saves short text entries. The app shows all published entries and the user can add a new entry by writing and publishing it.
The applcation has an string-array (string[]) type. Every item in the array is an entry that has to be displayed in the frontend entries list.
I know that I can't push to the array because that doesn't changes the state directly (and react doesn't notice that it have to be re-rendered). So i am using this way to get the new state: oldState.concat(newEntry). But React doesn't re-render it.
Here is my whole react code:
function App() {
const [entries, setEntries] = useState([] as string[])
const publish = (entry: string) => {
setEntries(entries.concat(entry))
}
return (
<div>
<Entries entries={entries} />
<EntryInput publish={publish} />
</div>
)
}
function Entries(props: { entries: string[] }) {
return (
<div className="entries">
{props.entries.map((v, i) => { <EntryDisplay msg={v} key={i} /> })}
</div>
)
}
function EntryInput(props: { publish: (msg: string) => void }) {
return (
<div className="entry-input">
<textarea placeholder="Write new entry..." id="input-new-entry" />
<button onClick={(e) => { props.publish((document.getElementById("input-new-entry") as HTMLTextAreaElement).value) }}>Publish</button>
</div>
)
}
function EntryDisplay(props: { msg: string }) {
return (
<div className="entry">{props.msg}</div>
)
}
const reactRoot = document.getElementById("react-root")
ReactDOM.render(<App />, reactRoot)
State is updated correctly, the return keyword is missing here:
function Entries(props: { entries: string[] }) {
return (
<div className="entries">
{props.entries.map((v, i) => {
// add missing 'return'
return <EntryDisplay msg={v} key={v} />
})}
</div>
)
}
Also, don't use array indexes as keys.